﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Microsoft.Practices.Unity;

namespace Might.Infrastructure.DOM {
    
    /// <summary>
    /// Basic implementation of universal DomNode used for almost everything :)
    /// </summary>
    public class DomNode : NotifyChangeBase, IDomNode {

        #region IDomNode Members

        public IDomNode Parent {
            get { return parent; }
            set {
                if (value == null || parent == value) {
                    return;
                }
                parent = value;
                OnChanged(this, "Parent");
            }
        }

        private IDomNode parent;
        
        public bool IsContainer { get; protected set; }

        // children

        //public IDomNodeCollection this[string groupName] {
        //    get {
        //        if (!children.ContainsKey(groupName)) {
        //            if (CanHasChildOfType(groupName)) {
        //                children.Add(groupName, new DomNodeCollection( groupName , this ) );
        //                children[groupName].CollectionChanged += DomNode_CollectionChanged;
        //            } else {
        //                throw new NotSupportedException(groupName);
        //            }
        //        }
        //        return children[groupName];
        //    }
        //}

        #region obsolete methods

        // although this method is marked obsolete, there should be some mechanism 
        // for identifiing, which group is valid and which not
        public bool CanHasChildOfType(string groupName) {
            // TODO
            return true;
        }
        
        public IDomNodeCollection GetChildren(string groupName) {
            if (!children.ContainsKey(groupName)) {
                children.Add(groupName, new DomNodeCollection( groupName , this ) );
                children[groupName].CollectionChanged += DomNode_CollectionChanged;
            }
            return children[groupName];
        }

        public IDomNode GetFirstChild(string groupName) {
            IDomNodeCollection col = GetChildren(groupName);
            if (col != null && col.Count > 0) {
                return col[0];
            }

            return null;
        }

        public void InsertChild(string groupName, IDomNode child) {
            if (child == null) {
                throw new ArgumentNullException("child");
            }

            if (!children.ContainsKey(groupName)) {
                children.Add(groupName, new DomNodeCollection( groupName , this ) );
                children[groupName].CollectionChanged += DomNode_CollectionChanged;
            }

            if (!children[groupName].Contains(child)) {
                IsContainer = true;
                child.Parent = this;
                children[groupName].Add(child);
            } else {
                throw new ArgumentException("Node already contains this child");
            }

        }

        public void RemoveChild(string groupName, IDomNode child) {
            //TODO ... here should be later some clean up
            if (children.ContainsKey(groupName) && children[groupName].Contains(child)) {
                children[groupName].Remove(child);
            }

        }

        public void EmptyGroup(string groupName) {
            //TODO ... here should be later some clean up
            if (children.ContainsKey(groupName)) {
                children[groupName].Clear();
            }
        }

        #endregion obsolete
        
        public string[] GetGroupNames() {

            string[] groups = new string[ children.Count ];            
            children.Keys.CopyTo( groups , 0 );
            
            return groups;                                    
            
        }

        public void ClearAllGroups() {
            //TODO ... here should be later some clean up
            foreach (KeyValuePair<string, DomNodeCollection> item in children) {
                item.Value.Clear();
            }
        }


        // and something about references :)

        /// <summary>
        /// Contains nodes for which are this node references.
        /// </summary>
        public List<IDomNode> DerivedTypes {
            get { return referenceFor; }
        }

        private readonly List<IDomNode> referenceFor = new List<IDomNode>();


        public void InsertReference( string refName , IDomReference reference ) {
            
            //TODO : checking refName according to reference names in file ReferenceGroupNames.cs
            
            if (reference == null) {
                throw new ArgumentNullException("reference");
            }                        
            
            if ( references.ContainsKey( refName ) ) {
                throw new ArgumentException( "This node already contains this reference name, reference name must be unique" );    
            } else {
                references.Add( refName , reference );
            }
            
        }

        public IDomReference GetReference( string refName ) {
            
            if( references.ContainsKey( refName ) ) {
                return references[ refName ];
            } 
            
            return null;
            
        }

        public List<IDomReference> GetReferences() {
            return references.Select(k => k.Value).ToList();
        }
        
        public void RemoveReference( string refName ) {
        
            if( references.ContainsKey( refName ) ) {
                references[ refName ].ClearReference();
                references.Remove( refName );                
            }                        
            
        }

        public void RemoveReference( IDomReference reference ) {

            foreach( var item in references ) {
                
                if( item.Value.Equals( reference ) ) {
                    reference.ClearReference();
                    references.Remove( item.Key );
                    break;
                }
                
            }                        

        }

        public void RemoveAllReferences() {            
            
            foreach( var item in references ) {                
                item.Value.ClearReference();                
            }
            
            references.Clear();
            
        }

        public void ReferenceDeleted( IDomNode node ) {
                
            foreach( var item in references ) {
                
                if( item.Value.Reference.Equals( node ) ) {
                    references.Remove( item.Key );
                    break;
                }
                
            } 
                           
        }

        public void SetAsReference( IDomNode node ) {
            if( !referenceFor.Contains( node ) ) {
                referenceFor.Add( node );
            }
        }
       
        public void RemoveAsReference( IDomNode node ) {
            if( referenceFor.Contains( node ) ) {
                referenceFor.Remove( node );
            }
        }
   

        #endregion


        #region Internals        

        /// <summary>
        /// Internal implementation for various groups of items
        /// </summary>
        private readonly Dictionary<string, DomNodeCollection> children = new Dictionary<string, DomNodeCollection>();

        /// <summary>
        /// Internal implementation of references
        /// </summary>
        private readonly Dictionary<string , IDomReference> references = new Dictionary<string , IDomReference>();


        private void DomNode_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
            OnChanged(this, "Children");
        }

        private void DomNodeReferences_CollectionChanged( object sender , NotifyCollectionChangedEventArgs e ) {
            OnChanged( this , "References" );
        }

        #endregion
    }
}
